package cn.conac.as.monitor.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;

import cn.conac.as.framework.utils.FastJsonUtil;
import cn.conac.as.framework.utils.HTMLFilter;
import cn.conac.as.framework.utils.IdGen;
import cn.conac.as.framework.utils.StringUtils;
import cn.conac.as.monitor.constant.AllowTest;
import cn.conac.as.monitor.constant.EnvironmentType;
import cn.conac.as.monitor.constant.ScanResultType;
import cn.conac.as.monitor.entity.BdtInterfaceEntity;
import cn.conac.as.monitor.entity.BdtInterfaceMonitorEntity;

public class HttpClientUtil {
	/**
     * 日志记录器
     */
    private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
	
	/**
	 * 接口响应正常时间
	 */
	private static final int NORMAL_TIME = Integer
			.parseInt(PropertiesUtil.getValue("config/config.properties", "interface.response.normal.time"));
	
	/**
	 * 接口响应警告时间
	 */
	private static final int WARNING_TIME = Integer
			.parseInt(PropertiesUtil.getValue("config/config.properties", "interface.response.warning.time"));
	
	/**
	 * HttpClient请求超时
	 */
	private static final int HTTPCLIENT_CONNECTION_TIMEOUT = Integer
			.parseInt(PropertiesUtil.getValue("config/config.properties", "httpclient.connection.timeout"));
	
	/**
	 * HttpClient请求超时
	 */
	private static final int HTTPCLIENT_CONNECTION_REQUEST_TIMEOUT = Integer
			.parseInt(PropertiesUtil.getValue("config/config.properties", "httpclient.connection.request.timeout"));
	
	/**
	 * HttpClient读取超时
	 */
	private static final int HTTPCLIENT_SO_TIMEOUT = Integer
			.parseInt(PropertiesUtil.getValue("config/config.properties", "httpclient.so.timeout"));
	
	/**
	 * 生产环境登录时使用的token
	 */
	private static final String PROD_ENV_LOGIN_TOKEN = PropertiesUtil.getValue("config/config.properties", "prod.env.login.token");
	
	/**
	 * 测试环境登录时使用的token
	 */
	private static final String TEST_ENV_LOGIN_TOKEN = PropertiesUtil.getValue("config/config.properties", "test.env.login.token");
	
	/**
	 * 被允许的接口操作类型
	 */
	private static final String ALLOW_OPERATION_TYPE=PropertiesUtil.getValue("config/config.properties", "interface.allow.operation.type");
	
	/**
	 * 发送 post请求，测试接口，根据测试接口的情况收集测试信息，最后返回BdtInterfaceMonitorEntity类型的对象
	 * 
	 * @param url
	 * @param params
	 * @return
	 */
	public static BdtInterfaceMonitorEntity post(BdtInterfaceEntity bdtInterfaceEntity, String params, String newToken) {
		logger.debug(bdtInterfaceEntity.getId());
		
		// 如果这种类型的操作接口不在本次测试的范围之内，则直接返回
		if (!isAllowOperationType(bdtInterfaceEntity)) {
			return null;
		}
		
		// 判断当前接口是否本次测试范围之内
		if (!isInTestRange(bdtInterfaceEntity)) {
			return null;
		}
		
		BdtInterfaceMonitorEntity imEntity = new BdtInterfaceMonitorEntity();

		CloseableHttpClient httpClient = HttpClients.createDefault();
		RequestConfig requestConfig = RequestConfig.custom()
		        .setConnectTimeout(HTTPCLIENT_CONNECTION_TIMEOUT).setConnectionRequestTimeout(HTTPCLIENT_CONNECTION_REQUEST_TIMEOUT)
		        .setSocketTimeout(HTTPCLIENT_SO_TIMEOUT).build();
		HttpPost httpPost = new HttpPost(bdtInterfaceEntity.getUrl());
		httpPost.setConfig(requestConfig);
		
		// 如果没有这个设置，汇报"error":"Unsupported Media Type"
		httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
		// 在header里存储token，否则没有权限
		if(bdtInterfaceEntity.getEnvironmentType().equals(EnvironmentType.PRODUCTION_ENVIRONMENT)){
			httpPost.setHeader("Authorization", newToken);
		}
		if(bdtInterfaceEntity.getEnvironmentType().equals(EnvironmentType.TEST_ENVIRONMENT)){
			httpPost.setHeader("Authorization", TEST_ENV_LOGIN_TOKEN);
		}
		
		logger.debug(bdtInterfaceEntity.getUrl());

		long scanTimeLong=0;
		long endTimeLong=0;
		double responseTime=0;
		try {
			if(null!=params){
				StringEntity strEntity = new StringEntity(params);
				httpPost.setEntity(strEntity);
			}
			
			// 调用接口，开始测试
			scanTimeLong=System.currentTimeMillis();
			CloseableHttpResponse response = httpClient.execute(httpPost);
			endTimeLong=System.currentTimeMillis();
			
			responseTime=((double)(endTimeLong-scanTimeLong)/1000);
			logger.debug("--------------------------------------" + responseTime);
			logger.debug("--------------------------------------" + scanTimeLong);
			logger.debug("--------------------------------------" + endTimeLong);
			
			Timestamp scanTime=new Timestamp(scanTimeLong);
			Timestamp endTime=new Timestamp(endTimeLong);
			
			imEntity.setId(IdGen.uuid());
			imEntity.setScanTime(scanTime);
			imEntity.setEndTime(endTime);
			imEntity.setResponseTime(String.valueOf(responseTime));
			imEntity.setInterfaceId(bdtInterfaceEntity.getId());
			HttpEntity entity = response.getEntity();
			String result=EntityUtils.toString(entity);
			logger.debug("测试的返回值： "+result);
			
			return handleTestResult(bdtInterfaceEntity, result, responseTime, imEntity);
		} catch (HttpHostConnectException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (ClientProtocolException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (UnsupportedEncodingException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (ParseException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (IOException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch(Exception e){
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} finally {
			// 关闭连接,释放资源
			try {
				httpClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 发送 get请求
	 */
	public static BdtInterfaceMonitorEntity get(BdtInterfaceEntity bdtInterfaceEntity, String newToken) {
		logger.debug(bdtInterfaceEntity.getId());
		
		// 如果这种类型的操作接口不在本次测试的范围之内，则直接返回
		if(!isAllowOperationType(bdtInterfaceEntity)){
			return null;
		}
		
		// 判断当前接口是否本次测试范围之内
		if (!isInTestRange(bdtInterfaceEntity)) {
			return null;
		}
			
		BdtInterfaceMonitorEntity imEntity = new BdtInterfaceMonitorEntity();
		
		CloseableHttpClient httpClient = HttpClients.createDefault();
		RequestConfig requestConfig = RequestConfig.custom()
		        .setConnectTimeout(HTTPCLIENT_CONNECTION_TIMEOUT).setConnectionRequestTimeout(HTTPCLIENT_CONNECTION_REQUEST_TIMEOUT)
		        .setSocketTimeout(HTTPCLIENT_SO_TIMEOUT).build();
		String url=bdtInterfaceEntity.getUrl();
		
		// 加载参数
		long scanTimeLong=0;
		long endTimeLong=0;
		double responseTime=0;
		try {
			if(null!=bdtInterfaceEntity.getParameterUseCase()){
				JSONObject obj=JSON.parseObject(bdtInterfaceEntity.getParameterUseCase(),Feature.OrderedField);
				Set keySet=obj.keySet();
				Iterator it=keySet.iterator();
				while(it.hasNext()){
					String key=(String) it.next();
					String value=(String) obj.get(key);
					logger.debug(key);
					logger.debug(value);
					url+="/"+value;
				}
			}
			logger.debug(url);
			
			HttpGet httpGet = new HttpGet(url);
			httpGet.setConfig(requestConfig);

			// 如果没有这个设置，汇报"error":"Unsupported Media Type"
			httpGet.setHeader("Content-Type", "application/json;charset=UTF-8");
			// 在header里存储token，否则没有权限
			if(bdtInterfaceEntity.getEnvironmentType().equals(EnvironmentType.PRODUCTION_ENVIRONMENT)){
				httpGet.setHeader("Authorization", newToken);
			}
			if(bdtInterfaceEntity.getEnvironmentType().equals(EnvironmentType.TEST_ENVIRONMENT)){
				httpGet.setHeader("Authorization", TEST_ENV_LOGIN_TOKEN);
			}
			
			// 调用接口，开始测试
			scanTimeLong=System.currentTimeMillis();
			CloseableHttpResponse response = httpClient.execute(httpGet);
			endTimeLong=System.currentTimeMillis();
			
			responseTime=((double)(endTimeLong-scanTimeLong)/1000);
			logger.debug("--------------------------------------" + responseTime);
			logger.debug("--------------------------------------" + scanTimeLong);
			logger.debug("--------------------------------------" + endTimeLong);
			
			Timestamp scanTime=new Timestamp(scanTimeLong);
			Timestamp endTime=new Timestamp(endTimeLong);
			
			imEntity.setId(IdGen.uuid());
			imEntity.setScanTime(scanTime);
			imEntity.setEndTime(endTime);
			imEntity.setResponseTime(String.valueOf(responseTime));
			imEntity.setInterfaceId(bdtInterfaceEntity.getId());
			HttpEntity entity = response.getEntity();
			String result=EntityUtils.toString(entity);
			logger.info("接口id： "+bdtInterfaceEntity.getId());
			logger.debug("测试的返回值： "+result);
			
			return handleTestResult(bdtInterfaceEntity, result, responseTime, imEntity);
		} catch (HttpHostConnectException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (ClientProtocolException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (UnsupportedEncodingException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (ParseException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch (IOException e) {
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} catch(Exception e){
			return handleException(e, scanTimeLong, bdtInterfaceEntity, imEntity);
		} finally {
			// 关闭连接,释放资源
			try {
				httpClient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 如果这种类型的操作接口不在本次测试的范围之内，则直接返回
	 * @param bdtInterfaceEntity
	 * @return
	 */
	private static boolean isAllowOperationType(BdtInterfaceEntity bdtInterfaceEntity){
		String[] allowOperationType = ALLOW_OPERATION_TYPE.split("");
		for (int i = 0; i < allowOperationType.length; i++) {
			if (!StringUtils.inString(bdtInterfaceEntity.getOperationType(), allowOperationType)) {
				logger.info("id: " + bdtInterfaceEntity.getUrl() + "   这种操作类型的接口不在本次测试范围之内");
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 判断当前接口是否本次测试范围之内
	 * @param bdtInterfaceEntity
	 * @return
	 */
	private static boolean isInTestRange(BdtInterfaceEntity bdtInterfaceEntity) {
		if (bdtInterfaceEntity.getAllowTest().equals(AllowTest.NOT_ALLOW_TEST)) {
			logger.info("id: " + bdtInterfaceEntity.getId() + "   这个接口不在本次测试范围之内");
			return false;
		}
		return true;
	}
	
	/**
	 * 处理接口测试结果
	 * @param bdtInterfaceEntity
	 * @param result
	 * @param responseTime
	 * @param imEntity
	 * @return
	 */
	private static BdtInterfaceMonitorEntity handleTestResult(BdtInterfaceEntity bdtInterfaceEntity, String result, 
			double responseTime, BdtInterfaceMonitorEntity imEntity) {
		// 表示调用接口后，返回的json字符串中code的值
		String code = null;
		// 表示当接口返回json字符串时，调用是否成功
		boolean jsonString = false;
		// 表示当接口返回json数组时，调用是否成功
		boolean jsonArray = false;
		// 表示当接口返回输入流时（下载文件），调用是否成功
		boolean inputStream = false;
		// 表示当接口返回html时，调用是否成功
		boolean htmlString = false;
		
		if (bdtInterfaceEntity.getSuccessCode().equals(ScanResultType.JSON_ARRAY)) {
			/* 当调用接口的返回值应该是个json数组时，如果url出错会返回html，如果没有数据或者参数出错会返回空数组 */
			if(HTMLFilter.isHtml(result)){
				jsonArray=false;
			}else{
				JSONArray array = JSON.parseArray(result);
				if (array.size() != 0) {
					jsonArray = true;
				}else{
					jsonArray = true;
					logger.info(bdtInterfaceEntity.getId()+"   返回空的json数组，或者参数错误");
				}
			}
		}else if (bdtInterfaceEntity.getSuccessCode().equals(ScanResultType.INPUT_STREAM)) {
			/* 当调用接口的返回值应该是输入流时，如果url出错、没有数据或者参数出错可能会返回html，也可能返回json字符串 */
			if (HTMLFilter.isHtml(result)) {
				// 当返回的是html时
				inputStream = false;
			}else if(FastJsonUtil.isJson(result)){
				// 当返回的是json字符串时，判断code是否存在
				JSONObject obj = JSON.parseObject(result);
				code = String.valueOf(obj.get(ScanResultType.RESULT_KEY));
				if(null==code || code.equals("null")){
					inputStream = false;
				}else{
					inputStream = true;
				}
			}else{
				inputStream = true;
			}
		} else {
			/* 当调用接口的返回值应该是个json字符串时，如果url出错、没有数据或者参数出错可能会返回html，也可能返回json字符串 */
			logger.info("getInterfaceId: "+imEntity.getInterfaceId());
			JSONObject obj = JSON.parseObject(result);
			code = String.valueOf(obj.get(ScanResultType.RESULT_KEY));
			if(code.equals(bdtInterfaceEntity.getSuccessCode())){
				jsonString = true;
			}else{
				jsonString = false;
			}
		}

		// 当返回的字符串为json字符串，并且code为各自接口的successCode时，才认为接口调用成功。
		// 或者返回的是个json数组，并且size不为0，也表示调用成功。
		// 当下载文件成功时，返回的是输入流，当失败时，返回的是html。
		if (jsonString || jsonArray || inputStream) {
			if (responseTime <= NORMAL_TIME) {
				// 正常
				imEntity.setScanResult(ScanResultType.NORMAL);
			} else if (NORMAL_TIME < responseTime && responseTime <= WARNING_TIME) {
				// 预警
				imEntity.setScanResult(ScanResultType.PREWARNING);
			} else if (responseTime > WARNING_TIME) {
				// 警告
				imEntity.setScanResult(ScanResultType.WARNING);
				imEntity.setErrorInfo(result);
			}
		} else {
			// 不可用
			imEntity.setScanResult(ScanResultType.DISABLE);
			imEntity.setErrorInfo(result);
		}
		return imEntity;
	}
	
	/**
	 * 当本程序中跑出异常时，将这个异常的堆栈信息存储到errorInfo中
	 * @param e
	 * @param scanTimeLong
	 * @param bdtInterfaceEntity
	 * @param imEntity
	 * @return
	 */
	private static BdtInterfaceMonitorEntity handleException(Exception e, long scanTimeLong, BdtInterfaceEntity bdtInterfaceEntity, BdtInterfaceMonitorEntity imEntity) {
		long endTimeLong=System.currentTimeMillis();
		
		double responseTime=((double)(endTimeLong-scanTimeLong)/1000);
		
		Timestamp scanTime=new Timestamp(scanTimeLong);
		Timestamp endTime=new Timestamp(endTimeLong);
		
		imEntity.setId(IdGen.uuid());
		imEntity.setScanTime(scanTime);
		imEntity.setEndTime(endTime);
		imEntity.setResponseTime(String.valueOf(responseTime));
		imEntity.setInterfaceId(bdtInterfaceEntity.getId());
		imEntity.setScanResult(ScanResultType.DISABLE);
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		e.printStackTrace(new PrintStream(baos));
		String exception = baos.toString();
		imEntity.setErrorInfo(exception);
		
		e.printStackTrace();
		
		return imEntity;
	}
}
